﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.ExpressionEvaluation
{
    /// <summary>
    /// This class provides the evaluation of a stack based expression like 1 2 + 3 - sin. It supports the operators 
    /// +, -, *, /, %, sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, ln, log, sqrt, exp, floor, ceiling, 
    /// truncate, pow and atan2.
    /// </summary>
    [Serializable]
    public class StackNotationExpressionEvaluator : AbstractExpressionEvaluator
    {
        /// <summary>
        /// The parser which holds the math expression.
        /// </summary>
        private StackNotationParser parser;

        /// <summary>
        /// Initializes a new instance of the <see cref="StackNotationExpressionEvaluator"/> class.
        /// </summary>
        /// <param name="parser">The parser which holds the math expression.</param>
        public StackNotationExpressionEvaluator(StackNotationParser parser)
        {
            if (parser == (StackNotationParser) null)
            {
                throw new ArgumentNullException("parser");
            }

            this.parser = parser;
        }

        /// <summary>
        /// Gets or sets the parser which holds the math expression.
        /// </summary>
        /// <value>The parser which holds the math expression.</value>
        public StackNotationParser Parser
        {
            get { return parser; }
            set { parser = value; }
        }

        /// <summary>
        /// Evaluates the specified mathematical term.
        /// </summary>
        /// <returns>The result of the mathematical term.</returns>
        public override double Evaluate()
        {
            this.parser.Reset();

            StackNotationSyntaxChecker syntaxChecker = new StackNotationSyntaxChecker(this.parser);

            if (!syntaxChecker.CheckSyntax())
            {
                throw new NotationSyntaxErrorException("The specified expression to evaluate has a syntax error.");
            }

            this.parser.Reset();

            double result = Double.Parse(this.parser.CurrentToken(), NumberStyles.Float, NumberFormatInfo.InvariantInfo);

            while (this.parser.HasMoreTokens)
            {
                this.parser.ShiftRight();

                if (OperatorInformation.IsOneParameterFunction(this.parser.CurrentToken()))
                {
                    if (this.parser.CurrentToken().Equals("sin"))
                    {
                        result = Math.Sin(result);
                    }
                    else if (this.parser.CurrentToken().Equals("cos"))
                    {
                        result = Math.Cos(result);
                    }
                    else if (this.parser.CurrentToken().Equals("tan"))
                    {
                        result = Math.Tan(result);
                    }
                    else if (this.parser.CurrentToken().Equals("sinh"))
                    {
                        result = Math.Sinh(result);
                    }
                    else if (this.parser.CurrentToken().Equals("cosh"))
                    {
                        result = Math.Cosh(result);
                    }
                    else if (this.parser.CurrentToken().Equals("tanh"))
                    {
                        result = Math.Tanh(result);
                    }
                    else if (this.parser.CurrentToken().Equals("asin"))
                    {
                        result = Math.Asin(result);
                    }
                    else if (this.parser.CurrentToken().Equals("acos"))
                    {
                        result = Math.Acos(result);
                    }
                    else if (this.parser.CurrentToken().Equals("atan"))
                    {
                        result = Math.Atan(result);
                    }
                    else if (this.parser.CurrentToken().Equals("ln"))
                    {
                        result = Math.Log(result);
                    }
                    else if (this.parser.CurrentToken().Equals("log"))
                    {
                        result = Math.Log10(result);
                    }
                    else if (this.parser.CurrentToken().Equals("sqrt"))
                    {
                        result = Math.Sqrt(result);
                    }
                    else if (this.parser.CurrentToken().Equals("exp"))
                    {
                        result = Math.Exp(result);
                    }
                    else if (this.parser.CurrentToken().Equals("floor"))
                    {
                        result = Math.Floor(result);
                    }
                    else if (this.parser.CurrentToken().Equals("ceiling"))
                    {
                        result = Math.Ceiling(result);
                    }
                    else if (this.parser.CurrentToken().Equals("truncate"))
                    {
                        result = Math.Truncate(result);
                    }
                }
                else if (OperatorInformation.IsTwoParameterFunction(this.parser.CurrentToken()))
                {
                    if (this.parser.CurrentToken().Equals("pow"))
                    {
                        result = Math.Pow(result,
                                          Double.Parse(this.parser.PreviousToken(), NumberStyles.Float,
                                                       NumberFormatInfo.InvariantInfo));
                    }
                    else if (this.parser.CurrentToken().Equals("atan2"))
                    {
                        result = Math.Atan2(result,
                                            Double.Parse(this.parser.PreviousToken(), NumberStyles.Float,
                                                         NumberFormatInfo.InvariantInfo));
                    }
                }
                else if (OperatorInformation.IsOperator(this.parser.CurrentToken()))
                {
                    double secondValue = Double.Parse(this.parser.PreviousToken(), NumberStyles.Float,
                                                      NumberFormatInfo.InvariantInfo);

                    if (this.parser.CurrentToken().Equals("+"))
                    {
                        result += secondValue;
                    }
                    else if (this.parser.CurrentToken().Equals("-"))
                    {
                        result -= secondValue;
                    }
                    else if (this.parser.CurrentToken().Equals("*"))
                    {
                        result *= secondValue;
                    }
                    else if (this.parser.CurrentToken().Equals("/"))
                    {
                        result /= secondValue;
                    }
                    else if (this.parser.CurrentToken().Equals("%"))
                    {
                        result %= secondValue;
                    }
                }
            }

            return result;
        }
    }
}